Java多线程系列

1、参考资料(多线程系列)

1、:Java多线程系列目录

1.1、基础篇

01. Java多线程系列--“基础篇”01之 基本概念

02. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式

03. Java多线程系列--“基础篇”03之 Thread中start()和run()的区别

04. Java多线程系列--“基础篇”04之 synchronized关键字

05. Java多线程系列--“基础篇”05之 线程等待与唤醒

06. Java多线程系列--“基础篇”06之 线程让步

07. Java多线程系列--“基础篇”07之 线程休眠 

08. Java多线程系列--“基础篇”08之 join()

09. Java多线程系列--“基础篇”09之 interrupt()和线程终止方式

10. Java多线程系列--“基础篇”10之 线程优先级和守护线程

11. Java多线程系列--“基础篇”11之 生产消费者问题

1.2、JUC原子类 

12. Java多线程系列--“JUC原子类”01之 框架 

13. Java多线程系列--“JUC原子类”02之 AtomicLong原子类

14. Java多线程系列--“JUC原子类”03之 AtomicLongArray原子类

15. Java多线程系列--“JUC原子类”04之 AtomicReference原子类

16. Java多线程系列--“JUC原子类”05之 AtomicLongFieldUpdater原子类

 1.3、JUC锁

17. Java多线程系列--“JUC锁”01之 框架

18. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

19. Java多线程系列--“JUC锁”03之 公平锁(一) 

20. Java多线程系列--“JUC锁”04之 公平锁(二) 

21. Java多线程系列--“JUC锁”05之 非公平锁 

22. Java多线程系列--“JUC锁”06之 Condition条件

23. Java多线程系列--“JUC锁”07之 LockSupport

24. Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock 

25. Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

26. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

27. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 

1.4、JUC集合

28. Java多线程系列--“JUC集合”01之 框架

29. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList

30. Java多线程系列--“JUC集合”03之 CopyOnWriteArraySet

31. Java多线程系列--“JUC集合”04之 ConcurrentHashMap

32. Java多线程系列--“JUC集合”05之 ConcurrentSkipListMap

33. Java多线程系列--“JUC集合”06之 ConcurrentSkipListSet

34. Java多线程系列--“JUC集合”07之 ArrayBlockingQueue

35. Java多线程系列--“JUC集合”08之 LinkedBlockingQueue 

36. Java多线程系列--“JUC集合”09之 LinkedBlockingDeque

37. Java多线程系列--“JUC集合”10之 ConcurrentLinkedQueue

1.5、JUC线程池

38. Java多线程系列--“JUC线程池”01之 线程池架构

39. Java多线程系列--“JUC线程池”02之 线程池原理(一)

40. Java多线程系列--“JUC线程池”03之 线程池原理(二)

41. Java多线程系列--“JUC线程池”04之 线程池原理(三)

42. Java多线程系列--“JUC线程池”05之 线程池原理(四)

43. Java多线程系列--“JUC线程池”06之 Callable和Future

2、简要总结

  多线程是Java中不可避免的一个重要主体。从本章开始,我们将展开对多线程的学习。接下来的内容,是对“JDK中新增JUC包”之前的Java多线程内容的讲解,涉及到的内容包括,Object类中的wait(), notify()等接口;Thread类中的接口;synchronized关键字。

注:JUC包是指,Java.util.concurrent包,它是由Java大师Doug Lea完成并在JDK1.5版本添加到Java中的。

2.1、线程状态

说明
线程共包括以下5种状态。
1. 新建状态(New)         : 线程对象被创建但还没有调用start方法时,处于新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked)  : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
    (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead)    : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程的5种状态涉及到的内容包括Object类, Thread和synchronized关键字。这些内容我们会在后面的章节中逐个进行学习。
Object类,定义了wait(), notify(), notifyAll()等休眠/唤醒函数。
Thread类,定义了一些列的线程操作函数。例如,sleep()休眠函数, interrupt()中断函数, getName()获取线程名称等。

Java线程状态:

注:其实Java在Thread类中定义了线程的状态(enum State),共有六种:NEW、RUNNABLE、TERMINATED、BLOCKED、WAITING、TIMED_WAITING。

 

  • RUNNABLE包括上述的Runnable和Running。
  • WAITING表示无线等待,需要被唤醒,如执行wait()、join()方法
  • TIMED_WAITING表示有限等待,在一定时间后自动唤醒,如执行wait(long timems)、join(long timems)、sleep(long timems)方法
  • BLOCKED表示阻塞,线程等待进入同步区时进入此状态

 

2.2、线程状态转换方法

详见:Java线程状态及 join、sleep、wait、notify、yield等的区别

2.3、同步互斥

同步:在多个线程并发访问共享数据时,保证数据在同一时刻只被一个(或一些,使用信号量的时候)线程使用。可以分为阻塞同步(如互斥)和非阻塞同步(需要硬件指令集的发展以保障“操作和冲突检测”的原子性,CAS操作等)。

互斥:实现同步的一种手段,属于阻塞同步。临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)是主要的互斥实现方式

互斥是因,同步是果;互斥是方法,同步是目的。

2.4、线程协作

待...

2.5、线程竞争

多线程共享内存(竞争访问同一资源)带来的问题:(参考资料:http://mp.weixin.qq.com/s/DC6JlRo7LEx4rRsz9U_Xpw

1、竞态条件(race condition):当多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,可能正确也可能不正确

解决:

  • 使用synchronized关键字(相关:synchronized关键字
  • 使用显式锁
  • 使用原子变量(计数、序号等场景)或原子更新(被更新的变量需被volatile修饰)
     1 // 2、原子变量
     2 // 对于count++这种操作来说,使用synchronzied成本太高了,需要先获取锁,最后还要释放锁,获取不到锁的情况下还要等待,还会有线程的上下文切换,这些都需要成本。
     3 //
     4 // 对于这种情况,完全可以使用原子变量代替,Java并发包中的基本原子变量类型有:
     5 // AtomicBoolean:原子Boolean类型
     6 // AtomicInteger:原子Integer类型
     7 // AtomicLong:原子Long类型
     8 // AtomicReference:原子引用类型
     9 class AtomicIntegerDemo {
    10     private static AtomicInteger counter = new AtomicInteger(0);
    11 
    12     static class Visitor extends Thread {
    13         @Override
    14         public void run() {
    15             for (int i = 0; i < 100; i++) {
    16                 counter.incrementAndGet();
    17                 Thread.yield();
    18             }
    19         }
    20     }
    21 
    22     public static void main(String[] args) throws InterruptedException {
    23         int num = 100;
    24         Thread[] threads = new Thread[num];
    25         for (int i = 0; i < num; i++) {
    26             threads[i] = new Visitor();
    27             threads[i].start();
    28         }
    29         for (int i = 0; i < num; i++) {
    30             threads[i].join();
    31         }
    32         System.out.println(counter.get());
    33     }
    34 }
    35 
    36 // 原子更新
    37 class FieldUpdaterDemo {
    38     static class DemoObject {// 类DemoObject中有两个成员num和ref,声明为volatile,但不是原子变量,不过DemoObject对外提供了原子更新方法compareAndSet,它是使用字段对应的FieldUpdater实现的,FieldUpdater是一个静态成员,通过newUpdater工厂方法得到,newUpdater需要的参数有类型、字段名、对于引用类型,还需要引用的具体类型。
    39         private volatile int num;
    40         private volatile Object ref;
    41 
    42         private static final AtomicIntegerFieldUpdater<DemoObject> numUpdater = AtomicIntegerFieldUpdater
    43                 .newUpdater(DemoObject.class, "num");
    44         private static final AtomicReferenceFieldUpdater<DemoObject, Object> refUpdater = AtomicReferenceFieldUpdater
    45                 .newUpdater(DemoObject.class, Object.class, "ref");
    46 
    47         public boolean compareAndSetNum(int expect, int update) {
    48             return numUpdater.compareAndSet(this, expect, update);
    49         }
    50 
    51         public int getNum() {
    52             return num;
    53         }
    54 
    55         public Object compareAndSetRef(Object expect, Object update) {
    56             return refUpdater.compareAndSet(this, expect, update);
    57         }
    58 
    59         public Object getRef() {
    60             return ref;
    61         }
    62     }
    63 
    64     public static void main(String[] args) {
    65         DemoObject obj = new DemoObject();
    66         obj.compareAndSetNum(0, 100);
    67         obj.compareAndSetRef(null, new String("hello"));
    68         System.out.println(obj.getNum());
    69         System.out.println(obj.getRef());
    70     }
    71 }
    View Code

2、内存可见性:多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到

解决:

  • 使用volatile关键字(相关:volatile关键字
  • 使用synchronized关键字同步
  • 使用显式锁同步

2.6、 对象的线程安全

1、线程安全:(Brian Goetz《Java Concurrency In Practice》)当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步或在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那此对象是线程安全的。

此定义要求线程安全的代码都必须具备一个特征:代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程的问题,更无须采取任何措施保证多线程的正确调用。此并不易做到,在大多数场景中,会将此定义弱化些,若把“调用这个对象的行为”限定为“单次调用”,此定义的其他描述也能成立,则称之是线程安全的了。

2、保证线程安全的思路

  1. 锁,使用synchronized或ReentrantLock(悲观策略),参看理解synchronized
  2. 原子操作,循环CAS(乐观策略),参看原子变量和CAS
  3. 写时拷贝,如CopyOnWriteArrayList,适用于元素个数不多,绝大部分访问都是读,且有大量并发线程要求读,只有个别线程进行写,且只是偶尔写的场合。(参看写时拷贝的List和Set

 

3、Java线程池(ThreadPoolExecutor)

需求演进(从需求出发理解Java线程池为什么这么设计,强烈推荐参阅这篇 公众号“低并发编程”的文章 。):

三个角色:线程、任务、队列

引入线程池和任务队列:若来一个任务创建一个线程处理,处理结束线程销毁,则线程的创建、销毁开销大,且能创建的线程数受系统限制。解决:启动固定个数(corePoolSize,使用者指定)的线程(称为核心线程)不销毁,并引入任务队列,各线程死循环从队列取任务执行、任务来时直接放入队列,若队列满则调用者等待。

优化:按需创建线程而非一开始就建corePoolSize个,即有任务时才建,直到线程数满额;支持指定队列的创建策略;支持指定队列满时的拒绝策略。

应对“短期任务量突增随后任务减少”的需求场景:基于这些任务的特点来改进线程池设计——线程池最大线程数扩大为maximumPoolSize,队列满后再来新任务时直接创建新线程(称为非核心线程)执行它,可再创建的线程数受限(maximumPoolSize-corePoolSize),若一个非核心线程在任务执行完后的一段时间内(keepAliveTime)没有接收到新任务则关闭该线程,这样就可应对短期任务高峰,且不至于长时间开启太多线程。

上述需求演进的最终结果就是jdk中线程池 ThreadPoolExecutor 的原理。

方法签名: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

参数:核心线程数、最大线程数、空闲线程的存活时间、该时间的单位、任务队列、线程创建的Fatory、任务拒绝策略。

内部整体任务提交流程:

四种默认拒绝策略:

ThreadPoolExecutor.AbortPolicy:这就是默认的方式,抛出异常
ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛异常,也不执行
ThreadPoolExecutor.DiscardOldestPolicy:将等待时间最长的任务扔掉,然后自己排队
ThreadPoolExecutor.CallerRunsPolicy:在任务提交者线程中执行任务,而不是交给线程池中的线程执行

JDK默认提供了四种线程池:

public static ExecutorService newSingleThreadExecutor() = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
public static ExecutorService newFixedThreadPool(int nThreads) = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
public static ExecutorService newCachedThreadPool() = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());//同步生产消费模型,队列大小为1的效果,即有人取走任务才能放新任务
public static ExecutorService ScheduledThreadPoolExecutor;//new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), handler);

注:

corePoolSize未满、队列满但maximumPoolSize未满 时,若有新任务到来,则都会新建线程来执行任务,且引起该新线程创建的任务是直接交由该线程执行而非进入队列,等该任务结束后线程才从队列取任务。可见,对于非核心线程,引起非核心线程创建的任务会比队列中的先被执行,从这角度看:这几个任务是违背队列“FIFO”的语义的,因为抢先执行了;这种任务的个数为非核心线程的个数。

可通过设置 allowCoreThreadTimeout 使得核心线程也可在 keepAliveTime 时长没接收到任务后销毁。

线程如何销毁?从队列取元素时可以设置超时时间,若在keepAliveTime时长内没拿到元素则销毁。相关源码:

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
View Code

Tomcat、Dubbo 等内部的线程池默认情况下会优先创建非核心线程,等maximumPoolSize满了才将任务放队列,这样就完全符合“FIFO语义”。

优点:

以工具类的形式为用户提供并行执行任务的能力,用户只需要负责提交任务而不需要考虑线程的创建、管理、执行等。

最大化提供灵活性,因为线程数、空闲线程存活时间、任务排队策略、线程的创建策略、任务拒绝策略等都可由用户指定。几乎所有的需求都可通过参数组合创建出满足需求的线程池。

参考资料:

Java线程池为设么这么设计1Java线程池为设么这么设计2——公众号低并发编程

Java线程池——老马说编程

 

 

 

posted @ 2016-03-15 21:39  March On  阅读(325)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)